{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "nPCp6G1zI-Ew"
      },
      "source": [
        "# Работа с Pandas\n",
        "\n",
        "\n",
        "## Series\n",
        "\n",
        "`Series` - это одна из структур данных библиотеки `pandas`. Она представляет собой что-то вроде словаря, однако, является упорядоченной.\n",
        "\n",
        "Создадим какой-нибудь список, а затем получим на его основе объект `Series`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {
        "id": "UUMvqVhwI-Ex"
      },
      "outputs": [],
      "source": [
        "import pandas as pd"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "r7BIt-kQI-E1"
      },
      "outputs": [],
      "source": [
        "a = [1, 3, 5, 7, 2]\n",
        "\n",
        "b = pd.Series(a)\n",
        "\n",
        "print(b)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RNvSFF_NI-E6"
      },
      "source": [
        "В результате такой операции получается объект `Series`, содержащий элементы из списка `a`. Здесь справа располагаются элементы из `a`, а слева - их индексы. Поскольку индексы для этих элементов мы явно не указали, используются стандартные.\n",
        "\n",
        "Индексы можно также указать явно, для этого нужно подать в качестве аргумента `index` список из индексов. Данный список должен быть той же длины, что и список `a`.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "akKQ3IiUI-FA"
      },
      "source": [
        "Индексы можно задать сразу, а можно и изменить позже:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ruk5GAE3I-FB"
      },
      "outputs": [],
      "source": [
        "b.index = [\"a\", \"b\", \"c\", \"d\", \"e\"]\n",
        "\n",
        "print(b)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YxxTvVIFI-FR"
      },
      "source": [
        "Индексы в `Series` не обязаны быть уникальными:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "65AeCMUbI-FS"
      },
      "outputs": [],
      "source": [
        "d = pd.Series(a, index=[0, 1, 0, 1, 0])\n",
        "\n",
        "print(d)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "G5Z7PCwmI-GK"
      },
      "source": [
        "### Добавление и удаление данных в Series\n",
        "\n",
        "С помощью метода `.append` мы можем добавлять к одному массиву `Series` другой:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "sHq5bhAII-GL"
      },
      "outputs": [],
      "source": [
        "g = d.append(b)\n",
        "\n",
        "print(g)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "1OnEv778I-GO"
      },
      "source": [
        "С помощью метода `.drop` мы можем удалять из массива элементы с определёнными индексами. Эти индексы мы и подаём в метод в виде списка:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "n_TqKZG_I-GP"
      },
      "outputs": [],
      "source": [
        "h = g.drop([0, \"d\"])\n",
        "\n",
        "print(h)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "B9GSACSLI-GR"
      },
      "source": [
        "Обратите внимание, что эти методы, в отличие от аналогичных методов из стандартных библиотек питона, не изменяют исходный массив, но возвращают новый.\n",
        "\n",
        "### Запись и чтение массивов Series из файла\n",
        "\n",
        "\n",
        "Для записи массивов `Series` в файлы используется формат файлов под названием `pickle`. Этот формат позволяет полностью сохранять питоновские объекты, а затем загружать их в неизменном виде.\n",
        "\n",
        "Для записи массива `Series` в файл используется метод `.to_pickle`, а для чтения - функция `np.read_pickle`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "metadata": {
        "id": "ZTrPh3ELI-GS"
      },
      "outputs": [],
      "source": [
        "h.to_pickle(\"h.pkl\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ZYirazZHI-GV"
      },
      "outputs": [],
      "source": [
        "h1 = pd.read_pickle(\"h.pkl\")\n",
        "\n",
        "print(h1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "VtiWsjwkI-GX"
      },
      "source": [
        "## DataFrame\n",
        "\n",
        "`DataFrame` - двумерная структура данных из библиотеки `pandas`, позволяющая удобно работать с таблицами.\n",
        "\n",
        "Самый простой способ задать `DataFrame` - с помощью словаря, в котором каждый ключ отвечает за столбец, а соответствующее значение - это список из элементов данного столбца. Эти списки должны иметь одинаковую длину."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Do-NbZz3I-GY"
      },
      "outputs": [],
      "source": [
        "a = {\n",
        "    \"col1\": [1, 2, 4, 5, 6, 7, 8],\n",
        "    \"col2\": [\"a\", \"c\", \"e\", \"g\", \"z\", \"x\", \"y\"]\n",
        "}\n",
        "\n",
        "b = pd.DataFrame(a)\n",
        "\n",
        "b"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5r6rUlqxI-Gb"
      },
      "source": [
        "С помощью атрибута `.shape` можно посмотреть форму массива `DataFrame`. Атрибут `.columns` содержит массив из столбцов, а `.index`, как и ранее, содержит массив индексов."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "d-BlBVFnI-Gc"
      },
      "outputs": [],
      "source": [
        "print(\"Форма b: {}\".format(b.shape))\n",
        "\n",
        "print(\"Столбцы b: {}\".format(b.columns))\n",
        "\n",
        "print(\"Индексы b: {}\".format(b.index))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pgn1tqBbI-Gf"
      },
      "source": [
        "Общую информацию о массиве можно запросить с помощью метода `.info`. Нам вернётся информация об индексах и столбцах данного массива, о том, какие типы данных хранятся в каждом из столбцов, а также информация о том, сколько памяти выделено под данный массив."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "6kE3cFoPI-Gg"
      },
      "outputs": [],
      "source": [
        "b.info()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "IKpjQKm0I-Gj"
      },
      "source": [
        "С помощью метода `.describe` можно получить некоторые статистические характеристики по столбцам с числовыми значениями: среднее значение, среднее квадратическое отклонение, максимум, минимум, квантили и пр."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "pDzeYsuQI-Gj"
      },
      "outputs": [],
      "source": [
        "b.describe()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "F4El81QKI-HS"
      },
      "source": [
        "### Случайный выбор значений из DataFrame\n",
        "\n",
        "Случайный выбор строк из массива `DataFrame` производится с помощью метода `.sample`. Вот несколько его важных параметров:\n",
        "\n",
        "* `frac` - какую долю от общего числа строк нужно вернуть (число от 0 до 1)\n",
        "* `n` - сколько строк нужно вернуть (число от 0 до числа строк в массиве)\n",
        "* `replace` - индикатор того, производится ли выбор _с возвращением_, т.е. с возможным повторением строк в выборке, или _без возвращения_ (`True` или `False`)\n",
        "\n",
        "Нельзя использовать параметры `frac` и `n` одновременно, нужно выбрать какой-то один."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "U2roX2B-I-HT"
      },
      "outputs": [],
      "source": [
        "b.sample(frac=0.5, replace=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "FsUnA0MdI-HZ"
      },
      "source": [
        "Если требуется просто перемешать всю выборку, это также можно выполнить с помощью метода `.sample`, передав в него параметр `frac=1`.\n",
        "\n",
        "### Запись и чтение DataFrame из файлов\n",
        "\n",
        "Для хранения таблиц широко распространён формат файлов с расширением `.csv`.\n",
        "\n",
        "Сохранить массив в файл можно с помощью метода `.to_csv`. Вот несколько важных параметров этого метода:\n",
        "\n",
        "* `sep` - символ, который нужно использовать для разделения значения столбцов между собой. По умолчанию это `\",\"`, но можно также использовать `\";\"`, `\"\\t\"` и др.\n",
        "* `index` - булево значение, индикатор того, нужно ли в файл сохранить также столбец индексов.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "metadata": {
        "id": "RYMORR5TI-Hb"
      },
      "outputs": [],
      "source": [
        "b.to_csv(\"test.csv\", sep=\";\", index=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "IyBxeXaVI-Hm"
      },
      "source": [
        "Прочитать массив из файла можно с помощью функции `pd.read_csv`. Здесь также можно указать разделитель столбцов в параметре `sep`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Lk_wWrT2I-Hm"
      },
      "outputs": [],
      "source": [
        "b1 = pd.read_csv(\"test.csv\", sep=\";\")\n",
        "\n",
        "b1"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "eFmm9O6UI-Hp"
      },
      "source": [
        "У данных команд для сохранения и чтения таблиц есть множество других важных и полезных параметров, поэтому рекомендуется также изучить их документацию: [to_csv](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_csv.html), [read_csv](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html).\n",
        "\n",
        "В `pandas` также имеются аналогичные команды для сохранения и записи таблиц как `excel` и `pickle`."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "yiL5JLa-I-Hp"
      },
      "source": [
        "## Работа с данными в Pandas\n",
        "\n",
        "### Слияние данных\n",
        "\n",
        "Рассмотрим следующий пример. Допустим, что мы работаем с небольшим отделом книжного магазина, в котором продаётся классическая литература. Наша задача - систематизировать ассортимент отдела.\n",
        "\n",
        "У нас есть таблица `authors`, содержащая данные об авторах: их идентификаторы (`author_id`) и имена (`author_name`):"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ol2u5eVDI-Hq"
      },
      "outputs": [],
      "source": [
        "authors = pd.DataFrame({\n",
        "    'author_id': [1, 2, 3],\n",
        "    'author_name': ['Пушкин', 'Толстой', 'Достоевский'],\n",
        "})\n",
        "\n",
        "authors"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6-IVLnBvI-Hs"
      },
      "source": [
        "Кроме того, у нас есть таблица `books`, содержащая информацию о книгах этих авторов. В этой таблице также есть колонка `author_id`, а также колонка `book_title`, содержащая название книги:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ESCfC0eaI-Hs"
      },
      "outputs": [],
      "source": [
        "books = pd.DataFrame({\n",
        "    'author_id': [2, 3, 3, 4],\n",
        "    'book_title': ['Война и мир', 'Идиот', 'Преступление и наказание',\n",
        "                   'Отцы и дети'],\n",
        "})\n",
        "\n",
        "books"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YrsRPVSEI-Hv"
      },
      "source": [
        "Что делать, если мы, например, захотим сопоставить названия книг именам их авторов? Для этого используется функция `pd.merge`: в эту функцию помещаются те таблицы, которые мы хотим соединить, а также несколько других важных аргументов:\n",
        "\n",
        "* `on` - параметр, отвечающий за то, какой столбец мы будем использовать для слияния,\n",
        "* `how` - каким образом производить слияние.\n",
        "\n",
        "Опишем подробнее, какие значения может принимать параметр `how`:\n",
        "\n",
        "* `\"inner\"` - внутреннее слияние. В этом случае в слиянии участвуют только те строки, которые присутствуют в обоих таблицах,\n",
        "* `\"left\"` - в слиянии участвуют все строки из левой таблицы,\n",
        "* `\"right\"` - то же самое, но для правой таблицы,\n",
        "* `\"outer\"` - внешнее слияние, соединяются все строки как из левой, так и из правой таблицы."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "8n2gin-nI-Hw"
      },
      "outputs": [],
      "source": [
        "pd.merge(authors, books, on='author_id', how='inner')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bVPmcn37I-Hy"
      },
      "source": [
        "Если мы выбираем `\"left\"`, `\"right\"` или `\"outer\"`, может случиться так, что строку из одной таблицы будет невозможно соединить со второй. Например, мы видим, что в нашей таблице `books` нет произведений Пушкина (его `id` равен 1). В свою очередь, в таблице `books` есть книга, для которой `author_id` равен 4, хотя, в таблице `authors` нет записи с таким `author_id`. Рассмотрим внешнее слияние этих таблиц:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "P9E4mInBI-Hz"
      },
      "outputs": [],
      "source": [
        "merged_df = pd.merge(authors, books, on='author_id', how='outer')\n",
        "\n",
        "merged_df"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Af0I2qVKI-H3"
      },
      "source": [
        "Как мы видим, в получившейся таблице присутствуют пропущенные значения (`NaN`).\n",
        "\n",
        "### Работа с пропущенными данными\n",
        "\n",
        "Пропущенные значения в `Series` или `DataFrame` можно получить с помощью метода `.isnull`. Наоборот, все имеющиеся непустые значения можно получить с помощью метода `.notnull`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "PIVEZX7sI-H3"
      },
      "outputs": [],
      "source": [
        "merged_df[merged_df[\"author_name\"].isnull()]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "F0MaLqu1I-H5"
      },
      "outputs": [],
      "source": [
        "merged_df[merged_df[\"author_name\"].notnull()]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pNq0X7x-I-H7"
      },
      "source": [
        "Заполнить пропущенные значения каким-то своим значением можно с помощью метода `.fillna()`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "76FEe8XjI-H8"
      },
      "outputs": [],
      "source": [
        "merged_df[\"author_name\"] = merged_df[\"author_name\"].fillna(\"unknown\")\n",
        "\n",
        "merged_df"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "nVrQXD-II-H-"
      },
      "source": [
        "### Добавление столбцов в `DataFrame`.\n",
        "\n",
        "Допустим, каждая из наших книг имеется в единственном экземпляре. Мы хотели бы создать в таблице `merged_df` столбец `quantity`, который бы содержал количество экземпляров каждой книги.\n",
        "\n",
        "Создание нового столбца в таблице `DataFrame` происходит аналогично созданию нового значения в словаре `dict`. Достаточно просто объявить значение `merged_df[\"quantity\"]`. Если подать в это значение какое-нибудь число или строку, то все значения в данном столбце приравняются к этому числу или строке. Также можно подать сюда список, тогда значения из этого списка поступят в соответствующие строки этого столбца. В этом случае длина списка обязана совпадать с числом строк таблицы.\n",
        "\n",
        "Итак, выберем все строки с непустым значением поля `book_title`, и для них запишем в столбец `quantity` число 1. Это можно сделать с помощью атрибута `.loc`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "qMczv7OTI-H-"
      },
      "outputs": [],
      "source": [
        "merged_df.loc[merged_df[\"book_title\"].notnull(), \"quantity\"] = 1\n",
        "\n",
        "merged_df"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "AOBt9xicI-IA"
      },
      "source": [
        "Теперь заполним все пропуски в этом столбце числом 0:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "lPJUXA6-I-IB"
      },
      "outputs": [],
      "source": [
        "merged_df[\"quantity\"].fillna(0, inplace=True)\n",
        "\n",
        "merged_df"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "y9n61GsAI-ID"
      },
      "source": [
        "Наконец, приведём значения в этом столбце к типу `int`. (Это сделать невозможно, если в столбце содержатся пропуски.)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "oiC1eYVvI-IE"
      },
      "outputs": [],
      "source": [
        "merged_df[\"quantity\"] = merged_df[\"quantity\"].astype(int)\n",
        "\n",
        "merged_df"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "S4WgroS-I-IG"
      },
      "source": [
        "В `DataFrame` можно использовать индексы по умолчанию, а можно и назначить свои. Например, в качестве индексов можно использовать какой-нибудь из столбцов:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "VfSnwKwsI-IG"
      },
      "outputs": [],
      "source": [
        "merged_df.set_index(\"author_id\", inplace=True)\n",
        "\n",
        "merged_df"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "a3YQjgpMI-II"
      },
      "source": [
        "Если что, индексы всегда можно сбросить. Тогда текущие индексы становятся столбцом:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5aABCnNqI-IJ"
      },
      "outputs": [],
      "source": [
        "merged_df.reset_index(inplace=True)\n",
        "\n",
        "merged_df"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jMLzx0I7I-IN"
      },
      "source": [
        "### Удаление данных\n",
        "\n",
        "Для удаления данных из `DataFrame` используется метод `.drop`. В этот метод подаётся метка элемента, который необходимо удалить (индекс строки или название столбца), а также ось `axis`. При `axis=0` удаляется строка, при значении `axis=1` - столбец:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "akegn7sVI-IO"
      },
      "outputs": [],
      "source": [
        "merged_df[\"price\"] = 500\n",
        "\n",
        "merged_df"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Iycv-BFlI-IT"
      },
      "outputs": [],
      "source": [
        "merged_df.drop(\"price\", axis=1, inplace=True)\n",
        "\n",
        "merged_df"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6iF3MhWUI-IW"
      },
      "source": [
        "Теперь удалим строку с индексом 1:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "XbMgSWZ_I-IX"
      },
      "outputs": [],
      "source": [
        "merged_df.drop(1, axis=0, inplace=True)\n",
        "\n",
        "merged_df"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "cHSeORq8I-IY"
      },
      "source": [
        "### Сортировка данных\n",
        "\n",
        "Вернём только что удалённую строку. Напомним, что для этого используется метод `.append`. Кстати, добавлять строки к `DataFrame` можно прямо в виде словарей `dict`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1GbbSJHeI-Ia"
      },
      "outputs": [],
      "source": [
        "merged_df = merged_df.append(\n",
        "    {\n",
        "        \"author_id\": 2,\n",
        "        \"author_name\": \"Толстой\",\n",
        "        \"book_title\": \"Война и мир\",\n",
        "        \"quantity\": 1,\n",
        "    },\n",
        "    ignore_index=True,\n",
        ")\n",
        "\n",
        "merged_df"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jnJVEYYJI-Ih"
      },
      "source": [
        "Параметр `ignore_index=True` подаётся сюда, чтобы индексы соединяемых таблиц не учитывались. В результирующей таблице будут использованы стандартные последовательные индексы, начинающиеся с 0.\n",
        "\n",
        "Отсортируем эту таблицу по столбцу `author_id`. Это делается с помощью метода `.sort_values`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "WlwQC7ZgI-Ih"
      },
      "outputs": [],
      "source": [
        "merged_df.sort_values(by=\"author_id\", inplace=True)\n",
        "\n",
        "merged_df"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "gKydz8lII-Ij"
      },
      "source": [
        "Чтобы сбросить индексы, воспользуемся уже известным методом `.reset_index`. В нашем случае, стоит подать в него аргумент `drop=True`, который означает, что текущий столбец из индексов не нужно сохранять в таблице, а можно удалить."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "FvYreG9bI-Ij"
      },
      "outputs": [],
      "source": [
        "merged_df.reset_index(drop=True, inplace=True)\n",
        "\n",
        "merged_df"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dfIIi0LGI-Im"
      },
      "source": [
        "### Соединение таблиц\n",
        "\n",
        "Для соединения таблиц можно пользоваться функцией `pd.concat`. С этой функцией мы уже знакомились, когда изучали библиотеку `numpy`. Здесь эта функция работает аналогичным образом: соединяет таблицы либо вертикально (если указан параметр `axis=0`), либо горизонтально (если `axis=1`).\n",
        "\n",
        "Соединение происходит с сохранением индексов, если не указан параметр `ignore_index=True`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Jc68kpVPI-Im"
      },
      "outputs": [],
      "source": [
        "df1 = pd.DataFrame({\n",
        "    'author_id': [3, 5],\n",
        "    'author_name': ['Достоевский', 'Чехов'],\n",
        "    'book_title': ['Бесы', 'Три сестры'],\n",
        "    'quantity': [2, 3],\n",
        "})\n",
        "\n",
        "df2 = pd.concat([merged_df, df1], axis=0, ignore_index=True)\n",
        "\n",
        "df2"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "G06O6mgeI-Iq"
      },
      "outputs": [],
      "source": [
        "df3 = pd.DataFrame(\n",
        "    {'price': [700, 450, 500, 400, 350]},\n",
        "    index=[1, 2, 3, 5, 6],\n",
        ")\n",
        "\n",
        "df4 = pd.concat([df2, df3], axis=1)\n",
        "\n",
        "df4"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "g7uNIdjiI-Ir"
      },
      "source": [
        "### Операции над таблицами\n",
        "\n",
        "Как и ранее с массивами `numpy` и `Series`, с таблицами `DataFrame` можно производить различные математические операции. Например, значения различных столбцов можно поэлементно перемножать, складывать и пр."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5e-ebivAI-Ir"
      },
      "outputs": [],
      "source": [
        "df4[\"total\"] = df4[\"quantity\"] * df4[\"price\"]\n",
        "\n",
        "df4"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NTI-BTHbI-It"
      },
      "source": [
        "С помощью метода `.nlargest` можно вывести несколько наибольших значений. Указывается то, сколько значений нужно вернуть, а также то, по какому именно значению нужно сортировать:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "qzIHHkGQI-It"
      },
      "outputs": [],
      "source": [
        "df4.nlargest(3, \"price\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "iTygSn5hI-Iv"
      },
      "source": [
        "Имеется также аналогичный метод `.nsmallest`."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rbPPZVFVI-I2"
      },
      "source": [
        "### Группировка данных\n",
        "\n",
        "Данные в таблице `DataFrame` можно группировать по повторяющимся значениям выбранного столбца. Группировка позволяет вычислять какие-то _агренированные_ значения, т.е. значения, полученные каким-то образом из групп других значений. Например, если мы захотим сгруппировать нашу таблицу по значениям `author_name`, то каждая группа будет содержать все строки с одинаковым значением `author_name`. По таким группам можно затем посчитать какую-нибудь агрегирующую функцию, например, сумму, среднее, минимум и др.\n",
        "\n",
        "Вот несколько способов это сделать. В первом случае мы просто выбираем конкретный столбец из группировки и применяем к нему какую-то агрегирующую функцию:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 45,
      "metadata": {
        "id": "2VTGNpTMI-I2"
      },
      "outputs": [],
      "source": [
        "groupby = df4.groupby(\"author_name\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "T1UW_htmI-I3"
      },
      "outputs": [],
      "source": [
        "groupby[\"price\"].mean()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hkYvCWkBI-I5"
      },
      "source": [
        "Второй способ - с помощью метода `.agg`. Данный метод является более гибким. Например, он позволяет вычислять одновременно несколько различных агрегирующих функций от разных столбцов:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gTWXVizZI-I5"
      },
      "outputs": [],
      "source": [
        "groupby.agg({\"price\": \"max\", \"total\": \"count\"})"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "provenance": []
    },
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.7.4"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}